﻿using Engine;
using Engine.Graphics;
using SharpDX;
using SharpDX.Direct3D11;
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace WorldRacing2.Material
{
    public class Wr2Material : Engine.Graphics.IMaterial
    {
        #region Private Fields

        static private string version = "1";
        private string bumpTextureName1 = string.Empty;

        private string bumpTextureName2 = string.Empty;

        private string bumpTextureName3 = string.Empty;

        private uint hashCode;

        private EnumMappingMode mappingMode;

        private MappingsWR2 mappingsWr2_1;

        private MappingsWR2 mappingsWr2_2;

        private MappingsWR2 mappingsWr2_3;

        private string textureName1 = string.Empty;

        private string textureName2 = string.Empty;

        private string textureName3 = string.Empty;

        #endregion Private Fields

        #region Public Constructors

        public Wr2Material()
        {
            this.mappingsWr2_1 = new MappingsWR2(0, 0, 0, 1, 1);
            this.mappingsWr2_2 = new MappingsWR2(0, 0, 0, 1, 1);
            this.mappingsWr2_3 = new MappingsWR2(0, 0, 0, 1, 1);
        }

        /// <summary>
        /// Creates a Material. Texture names should be given without fileextension.
        /// </summary>
        /// <param name="TextureID1"></param>
        /// <param name="TextureID2"></param>
        /// <param name="TextureID3"></param>
        /// <param name="mappings"></param>
        public Wr2Material(string TextureName1, string TextureName2, string TextureName3, EnumMappingMode mappings, MaterialManager materialManager)
        {
            this.textureName1 = TextureName1;
            this.textureName2 = TextureName2;
            this.textureName3 = TextureName3;
            this.TextureID1 = materialManager.GetTextureID(TextureName1);
            this.TextureID2 = materialManager.GetTextureID(TextureName2);
            this.TextureID3 = materialManager.GetTextureID(TextureName3);
            this.mappingMode = mappings;
        }

        public Wr2Material(int TextureID1, int TextureID2, int TextureID3, EnumMappingMode enumMappingMode, MaterialManager materialManager)
            : this(materialManager.TextureNames[TextureID1], materialManager.TextureNames[TextureID2], materialManager.TextureNames[TextureID3], enumMappingMode, materialManager)
        {
            this.TextureID1 = TextureID1;
            this.TextureID2 = TextureID2;
            this.TextureID3 = TextureID3;
            UpdateHashCode();
        }

        public Wr2Material(int TextureID1, int TextureID2, int TextureID3, EnumMappingMode enumMappingMode, MaterialManager materialManager, MappingsWR2 mappingTexture1, MappingsWR2 mappingTexture2, MappingsWR2 mappingTexture3)
            : this(materialManager.TextureNames[TextureID1], materialManager.TextureNames[TextureID2], materialManager.TextureNames[TextureID3], enumMappingMode, materialManager, mappingTexture1, mappingTexture2, mappingTexture3)
        {
            this.TextureID1 = TextureID1;
            this.TextureID2 = TextureID2;
            this.TextureID3 = TextureID3;
            UpdateHashCode();
        }

        public Wr2Material(string TextureName1, string TextureName2, string TextureName3, EnumMappingMode mappings, MaterialManager materialManager, MappingsWR2 mappingTexture1, MappingsWR2 mappingTexture2, MappingsWR2 mappingTexture3)
            : this(TextureName1, TextureName2, TextureName3, mappings, materialManager)
        {
            this.mappingsWr2_1 = mappingTexture1;
            this.mappingsWr2_2 = mappingTexture2;
            this.mappingsWr2_3 = mappingTexture3;

            UpdateHashCode();
        }

        public Wr2Material(Wr2Material material)
        {
            this.bumpTextureName1 = material.bumpTextureName1;
            this.bumpTextureName2 = material.bumpTextureName2;
            this.bumpTextureName3 = material.bumpTextureName3;
            this.mappingMode = material.mappingMode;
            this.mappingsWr2_1 = material.mappingsWr2_1;
            this.mappingsWr2_2 = material.mappingsWr2_2;
            this.mappingsWr2_3 = material.mappingsWr2_3;
            this.TextureID1 = material.TextureID1;
            this.TextureID2 = material.TextureID2;
            this.TextureID3 = material.TextureID3;
            this.TextureName1 = material.TextureName1;
            this.TextureName2 = material.TextureName2;
            this.TextureName3 = material.TextureName3;

            UpdateHashCode();
        }

        #endregion Public Constructors

        #region Internal Events

        internal event EventHandler PropertiesChanged;

        #endregion Internal Events

        #region Public Properties

        [XmlIgnoreAttribute]
        public string BumpTextureName1
        {
            get
            {
                return this.bumpTextureName1;
            }
            set
            {
                this.bumpTextureName1 = value;
                UpdateHashCode();
            }
        }

        [XmlIgnoreAttribute]
        public string BumpTextureName2
        {
            get
            {
                return this.bumpTextureName2;
            }
            set
            {
                this.bumpTextureName2 = value;
                UpdateHashCode();
            }
        }

        [XmlIgnoreAttribute]
        public string BumpTextureName3
        {
            get
            {
                return this.bumpTextureName3;
            }
            set
            {
                this.bumpTextureName3 = value;
                UpdateHashCode();
            }
        }

        int IMaterial.MappingMode
        {
            get
            {
                return (int)this.mappingMode;
            }
            set
            {
                this.mappingMode = (EnumMappingMode)value;
            }
        }

        public EnumMappingMode MappingMode
        {
            get
            {
                return this.mappingMode;
            }
            set
            {
                this.mappingMode = value;
                UpdateHashCode();
            }
        }

        public MappingsWR2 Mappings1
        {
            get
            {
                return this.mappingsWr2_1;
            }
            set
            {
                this.mappingsWr2_1 = value;
                UpdateHashCode();
            }
        }

        public MappingsWR2 Mappings2
        {
            get
            {
                return this.mappingsWr2_2;
            }
            set
            {
                this.mappingsWr2_2 = value;
                UpdateHashCode();
            }
        }

        public MappingsWR2 Mappings3
        {
            get
            {
                return this.mappingsWr2_3;
            }
            set
            {
                this.mappingsWr2_3 = value;
                UpdateHashCode();
            }
        }

        [XmlIgnoreAttribute]
        public int TextureID1 { get; set; }

        [XmlIgnoreAttribute]
        public int TextureID2 { get; set; }

        [XmlIgnoreAttribute]
        public int TextureID3 { get; set; }

        public string TextureName1
        {
            get
            {
                return this.textureName1;
            }
            set
            {
                this.textureName1 = value;
                this.TextureID1 = -1;
                UpdateHashCode();
            }
        }

        public string TextureName2
        {
            get
            {
                return this.textureName2;
            }
            set
            {
                this.textureName2 = value;
                this.TextureID2 = -1;
                UpdateHashCode();
            }
        }

        public string TextureName3
        {
            get
            {
                return this.textureName3;
            }
            set
            {
                this.textureName3 = value;
                this.TextureID3 = -1;
                UpdateHashCode();
            }
        }

        #endregion Public Properties

        #region Public Methods

        public static Wr2Material FromStream(BinaryReader br, MaterialManager materialManager)
        {
            Wr2Material material = new Wr2Material(br.ReadString(),
                                             br.ReadString(),
                                             br.ReadString(),
                                            (EnumMappingMode)br.ReadByte(),
                                            materialManager,
                                             MappingsWR2.FromStream(br),
                                             MappingsWR2.FromStream(br),
                                             MappingsWR2.FromStream(br)
                                             );

            //material.MappingMode = EnumMappingMode.material00;
            //material.UpdateHashCode();

            return material;
        }

        public static Wr2Material FromXml(StreamReader sr)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Wr2Material));
            Wr2Material material = (Wr2Material)xs.Deserialize(sr);

            material.UpdateHashCode();
            return material;
        }

        public static IMaterial FromXmlStream(XmlReader reader, MaterialManager materialManager)
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element
                            && reader.Name == "Wr2Material")
                {
                    Wr2Material material = new Wr2Material(reader.GetAttribute("Texture1"),
                        reader.GetAttribute("Texture2"),
                        reader.GetAttribute("Texture3"),
                        (EnumMappingMode)Convert.ToInt32(reader.GetAttribute("MappingMode")), materialManager);

                    material.UpdateTextureName(materialManager);

                    int currentMapping = 1;
                    while (reader.Read() && ((reader.Name != "Material") && (reader.NodeType != XmlNodeType.EndElement)))
                    {
                        if (reader.Name == "MaterialMapping" && currentMapping == 1)
                        {
                            material.mappingsWr2_1 = MappingsWR2.FromXmlReader(reader);
                            currentMapping++;
                            continue;
                        }
                        if (reader.Name == "MaterialMapping" && currentMapping == 2)
                        {
                            material.mappingsWr2_2 = MappingsWR2.FromXmlReader(reader);
                            currentMapping++;
                            continue;
                        }
                        if (reader.Name == "MaterialMapping" && currentMapping == 3)
                        {
                            material.mappingsWr2_3 = MappingsWR2.FromXmlReader(reader);
                            currentMapping++;
                            continue;
                        }
                    }
                    material.UpdateHashCode();
                    return material;
                }
            }
            throw new Exception("No Wr2 Material found at the position in xml-file: " + reader.Name);
        }

        public Vector4 GetMappings1A()
        {
            return this.mappingsWr2_1.mappings1;
        }

        public Vector4 GetMappings1B()
        {
            return this.mappingsWr2_1.mappings2;
        }

        public Vector4 GetMappings2A()
        {
            return this.mappingsWr2_2.mappings1;
        }

        public Vector4 GetMappings2B()
        {
            return this.mappingsWr2_2.mappings2;
        }

        public Vector4 GetMappings3A()
        {
            return this.mappingsWr2_3.mappings1;
        }

        public Vector4 GetMappings3B()
        {
            return this.mappingsWr2_3.mappings2;
        }

        public uint GetMaterialHashCode()
        {
            return this.hashCode;
        }

        public string[] GetUsedTextureNames()
        {
            return new string[] {
                 this.bumpTextureName1,
                 this.bumpTextureName2,
                 this.bumpTextureName3,
                 this.textureName1,
                 this.textureName2,
                 this.textureName3
            };
        }

        public void ReadXml(XmlReader xmlReader)
        {
        }

        public void SaveToStream(BinaryWriter bw)
        {
            bw.Write(Path.GetFileNameWithoutExtension(this.textureName1));
            bw.Write(Path.GetFileNameWithoutExtension(this.textureName2));
            bw.Write(Path.GetFileNameWithoutExtension(this.textureName3));
            bw.Write((byte)this.mappingMode);
            this.mappingsWr2_1.SaveToStream(bw);
            this.mappingsWr2_2.SaveToStream(bw);
            this.mappingsWr2_3.SaveToStream(bw);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("T1: " + this.textureName1);
            sb.Append(" T2: " + this.textureName2);
            sb.Append(" T3: " + this.textureName3);
            //sb.Append("  Tex1: " + Path.GetFileNameWithoutExtension(MaterialManager.TexturePaths[wr2Surface.texture1ID]));
            //sb.Append("  Tex2: " + Path.GetFileNameWithoutExtension(MaterialManager.TexturePaths[wr2Surface.texture2ID]));
            //sb.Append("  Tex3: " + Path.GetFileNameWithoutExtension(MaterialManager.TexturePaths[wr2Surface.texture3ID]));
            sb.Append(" " + ((EnumMappingMode)this.mappingMode).ToString());
            return sb.ToString();
        }

        public string ToXml(XmlTextWriter writer)
        {
            writer.WriteComment("Material for Scenery Editor V1.0");

            writer.WriteStartElement("Wr2Material");

            writer.WriteAttributeString("Texture1", Path.GetFileNameWithoutExtension(this.textureName1));
            writer.WriteAttributeString("Texture2", Path.GetFileNameWithoutExtension(this.textureName2));
            writer.WriteAttributeString("Texture3", Path.GetFileNameWithoutExtension(this.textureName3));

            writer.WriteAttributeString("MappingMode", ((byte)this.mappingMode).ToString());

            this.mappingsWr2_1.ToXmlStream(writer);
            this.mappingsWr2_2.ToXmlStream(writer);
            this.mappingsWr2_3.ToXmlStream(writer);

            writer.WriteEndElement();

            return "Hallo";
        }

        public void ToXmlStream(XmlWriter writer)
        {
            writer.WriteComment("Material for Scenery Editor V1.0");

            writer.WriteStartElement("Wr2Material");

            writer.WriteAttributeString("Texture1", Path.GetFileNameWithoutExtension(this.textureName1));
            writer.WriteAttributeString("Texture2", Path.GetFileNameWithoutExtension(this.textureName2));
            writer.WriteAttributeString("Texture3", Path.GetFileNameWithoutExtension(this.textureName3));

            writer.WriteAttributeString("MappingMode", ((byte)this.mappingMode).ToString());

            this.mappingsWr2_1.ToXmlStream(writer);
            this.mappingsWr2_2.ToXmlStream(writer);
            this.mappingsWr2_3.ToXmlStream(writer);

            writer.WriteEndElement();
        }

        public void UpdateTextureName(MaterialManager materialManager)
        {
            if (this.TextureID1 > -1)
                this.textureName1 = materialManager.TextureNames[this.TextureID1];
            if (this.TextureID2 > -1)
                this.textureName2 = materialManager.TextureNames[this.TextureID2];
            if (this.TextureID3 > -1)
                this.textureName3 = materialManager.TextureNames[this.TextureID3];
        }

        #endregion Public Methods

        #region Internal Methods

        internal ShaderResourceView GetBumpTexture1(MaterialManager materialManager)
        {
            int index = materialManager.GetTextureID(this.bumpTextureName1);
            if (index > -1)
            {
                //index = 109;
                return materialManager.Textures[index];
            }
            return materialManager.DummyNormalMap;
        }

        internal ShaderResourceView GetBumpTexture2(MaterialManager materialManager)
        {
            int index = materialManager.GetTextureID(this.bumpTextureName2);
            if (index > -1)
            {
                //index = 109;
                return materialManager.Textures[index];
            }
            return materialManager.DummyNormalMap;
        }

        internal ShaderResourceView GetBumpTexture3(MaterialManager materialManager)
        {
            int index = materialManager.GetTextureID(this.bumpTextureName3);
            if (index > -1)
            {
                //index = 109;
                return materialManager.Textures[index];
            }
            return materialManager.DummyNormalMap;
        }

        internal ShaderResourceView GetTexture1(MaterialManager materialManager)
        {
            return materialManager.Textures[materialManager.GetTextureID(this.textureName1)];
        }

        internal ShaderResourceView GetTexture2(MaterialManager materialManager)
        {
            return materialManager.Textures[materialManager.GetTextureID(this.textureName2)];
        }

        internal ShaderResourceView GetTexture3(MaterialManager materialManager)
        {
            return materialManager.Textures[materialManager.GetTextureID(this.textureName3)];
        }

        internal void UpdateTextureIDs(MaterialManager materialManager)
        {
            this.TextureID1 = materialManager.GetTextureID(this.textureName1);
            this.TextureID2 = materialManager.GetTextureID(this.textureName2);
            this.TextureID3 = materialManager.GetTextureID(this.textureName3);
        }

        #endregion Internal Methods

        #region Private Methods

        private void UpdateHashCode()
        {
            AdlerChecksum adlerChecksum = new AdlerChecksum();
            byte[] data = new byte[40];

            BitConverter.GetBytes(this.textureName1.GetHashCode()).CopyTo(data, 0);
            BitConverter.GetBytes(this.textureName2.GetHashCode()).CopyTo(data, 4);
            BitConverter.GetBytes(this.textureName3.GetHashCode()).CopyTo(data, 8);
            BitConverter.GetBytes(this.mappingsWr2_1.CRC).CopyTo(data, 12);
            BitConverter.GetBytes(this.mappingsWr2_2.CRC).CopyTo(data, 16);
            BitConverter.GetBytes(this.mappingsWr2_3.CRC).CopyTo(data, 20);
            BitConverter.GetBytes((int)this.mappingMode).CopyTo(data, 24);
            BitConverter.GetBytes(this.bumpTextureName1.GetHashCode()).CopyTo(data, 28);
            BitConverter.GetBytes(this.bumpTextureName2.GetHashCode()).CopyTo(data, 32);
            BitConverter.GetBytes(this.bumpTextureName3.GetHashCode()).CopyTo(data, 36);
            adlerChecksum.MakeForBuff(data);

            this.hashCode = adlerChecksum.ChecksumValue;//
        }

        #endregion Private Methods
    }
}